#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sobjc-commons/defines.h>
#include <sobjc-commons/utils.h>
#include <sobjc-commons/st.h>
#include "class.h"
#include "command.h"
#include "context.h"
#include "name.h"
#include "parse.h"
#include "scope.h"
#include "type.h"

struct class_iter {
  int n;
  objc_class_t *classes;
};

static int distribute_dispatch_codes(objc_class_t, BOOL);

static int iter_class_callback(char *, objc_class_t, struct class_iter *);
static st_table_t class_table = NULL;
static st_table_t sig_table = NULL;
static int nclasses = 0;

void
objc_init_class()
{
  class_table = st_init_strtable();
  sig_table = st_init_strtable();
  nclasses = 0;
}

void
objc_register_class(objc_class_t cls)
{
  cls->class_id = nclasses++;
  st_insert(class_table, cls->name, cls);
}

objc_class_t
objc_get_class(char *name)
{
  objc_class_t cls = NULL;

  st_lookup(class_table, name, (st_data_t *)&cls);

  return cls;
}

void
objc_iter_classes(objc_class_iter_f f, void *buf)
{
  struct class_iter it;
  int i;

  it.n = 0;
  it.classes = ALLOC_N(objc_class_t, nclasses);
  st_foreach(class_table, iter_class_callback, &it);

  for (i = 0; i < nclasses; i++)
    f(it.classes[i], buf);
}

static int
iter_class_callback(char *name __attribute((unused)),
                    objc_class_t cls, struct class_iter *it)
{
  it->n++;
  it->classes[cls->class_id] = cls;

  if (it->n <= nclasses)
    return ST_CONTINUE;
  else
    return ST_STOP;
}

const char *
objc_get_const_str_class_name()
{
  if (objc_main_opts.cstr_class)
    return objc_main_opts.cstr_class;
  else
    return OBJC_DEFAULT_CONST_STR_CLASS_NAME;
}

objc_class_t
objc_get_const_str_class()
{
  return objc_get_class(objc_get_const_str_class_name());
}

objc_class_t
objc_make_class(const char *name, objc_class_t superclass)
{
  objc_class_t cls;

  cls = ALLOC(struct objc_class);
  cls->class_id = -1;
  cls->name = objc_strdup(name);
  cls->getter = objc_make_get_class_object_func_name(name);
  cls->has_impl = NO;
  cls->superclass = superclass;
  cls->ivars = NULL;
  cls->ivars_repr = NULL;
  cls->inst_meths = NULL;
  cls->class_meths = NULL;
  cls->protocols = NULL;
  cls->categories = NULL;
  cls->is_abstract = NO;
  cls->is_sealed = NO;
  cls->inst_dcodes = NULL;
  cls->class_dcodes = NULL;

  return cls;
}

objc_ivar_t
objc_class_get_ivar(objc_class_t cls, const char *name)
{
  objc_ivar_list_t ivars;

  ivars = cls->ivars;
  while (ivars) {
    if (STREQ(ivars->car->name, name))
        return ivars->car;
    ivars = ivars->cdr;
  }

  if (cls->superclass != NULL)
    return objc_class_get_ivar(cls->superclass, name);
  else
    return NULL;
}

objc_method_t
objc_class_get_method(objc_class_t cls, const char *name, BOOL is_inst)
{
  objc_method_list_t meths;

  if (is_inst)
    meths = cls->inst_meths;
  else
    meths = cls->class_meths;

  while (meths) {
    if (STREQ(meths->car->sig->name, name))
      return meths->car;
    meths = meths->cdr;
  }

  if (cls->superclass != NULL)
    return objc_class_get_method(cls->superclass, name, is_inst);
  else
    return NULL;
}

objc_method_sig_t
objc_class_get_method_sig(objc_class_t cls, const char *name, BOOL is_inst)
{
  objc_method_t meth;

  meth = objc_class_get_method(cls, name, is_inst);
  if (meth)
    return meth->sig;
  else
    return NULL;
}

BOOL
objc_class_is_kind_of(objc_class_t cls, objc_class_t base)
{
  while (cls) {
    if (cls == base)
      return YES;
    cls = cls->superclass;
  }
  return NO;
}

char *
objc_method_sig_make_imp_ctype(objc_method_sig_t sig,
                               const char *recv_type,
                               objc_class_t cls,
                               BOOL is_inst)
{
  char *ctype, *buf;
  size_t len;
  int i;

  len = strlen(sig->mctype);
  for (i = 0; i < sig->nargs; i++)
    len += strlen(sig->pctypes[i]);
  if (cls)
    len += strlen(cls->name);
  len += 12;
  ctype = buf = ALLOC_N(char, len);

  *buf++ = OBJC_CTYPE_FUNC_BEGIN_CODE;
  if (is_inst)
    *buf++ = OBJC_CTYPE_ID_CODE;
  else
    *buf++ = OBJC_CTYPE_CLASS_CODE;

  if (cls)
    buf += sprintf(buf, "{%s}", cls->name);

  buf += sprintf(buf, ";%c;", OBJC_CTYPE_SEL_CODE);
  for (i = 0; i < sig->nargs; i++)
    buf += sprintf(buf, "%s;", sig->pctypes[i]);

  if (sig->vargs)
    *buf++ = OBJC_CTYPE_ELLIPSIS_CODE;
  *buf++ = OBJC_CTYPE_FUNC_END_CODE;

  if (*sig->mctype == OBJC_CTYPE_INSTANCE_CODE) {
    if (!is_inst)
      buf += sprintf(buf, "%c{%s}", OBJC_CTYPE_ID_CODE, cls->name);
    else
      objc_print_error("objc_method_sig_make_imp_ctype(): not class type for 'Instance'");
  } else if (*sig->mctype == OBJC_CTYPE_XSELF_CODE)
    buf += sprintf(buf, "%s", recv_type);
  else
    buf += sprintf(buf, "%s", sig->mctype);

  return ctype;
}

int
objc_get_dispatch_code(objc_class_t cls, const char *meth_name, BOOL is_inst)
{
  int code;
  st_table_t dcodes;

  if (is_inst)
    dcodes = cls->inst_dcodes;
  else
    dcodes = cls->class_dcodes;

  if (dcodes == NULL) {
    distribute_dispatch_codes(cls, is_inst);
    if (is_inst)
      dcodes = cls->inst_dcodes;
    else
      dcodes = cls->class_dcodes;
  }

  if (st_lookup(dcodes, meth_name, (st_data_t *)&code))
    return code;
  else if (cls->superclass)
    return objc_get_dispatch_code(cls->superclass, meth_name, is_inst);
  else
    return -1;
}

static int
distribute_dispatch_codes(objc_class_t cls, BOOL is_inst)
{
  st_table_t dcodes;
  int n;
  objc_method_list_t meths;

  if (cls->superclass) {
    if (is_inst)
      if (cls->superclass->inst_dcodes == NULL)
        n = distribute_dispatch_codes(cls->superclass, is_inst);
      else
        n = cls->superclass->inst_dcodes->num_entries;
    else 
      if (cls->class_dcodes == NULL)
        n = distribute_dispatch_codes(cls->superclass, is_inst);
      else
        n = cls->superclass->class_dcodes->num_entries;
  } else
    n = 0;

  if (is_inst)
    meths = cls->inst_meths;
  else
    meths = cls->class_meths;

  dcodes = st_init_strtable();
  while (meths) {
    st_add_direct(dcodes, meths->car->sig->name, (st_data_t)n++);
    meths = meths->cdr;
  }

  if (is_inst)
    cls->inst_dcodes = dcodes;
  else
    cls->class_dcodes = dcodes;

  return n;
}

